6.2. Zoom and rotate gestures
One of the coolest features of the iPhone when it was
presented as a new phone was the zoom and rotate gesture. Using a
pinching gesture with two fingers, the user can zoom in and zoom out
on content (generally a picture), and using two fingers moving in a
circle, he can rotate that picture.
Note:
For non-multitouch devices, we should provide zoom features
using normal floating buttons or with a slider.
Fortunately, from iOS 2.0, Safari allows us to detect these
gestures without using low-level math in the touch events. There are
three WebKit extensions available as events, listed in Table 35. The Android browser
has also added support for these events.
Table 35. Events available for touch handling
Event | Description |
---|
ongesturestart | Fired when the user
starts a gesture using two fingers |
ongesturechange | Fired when the user is
moving her fingers, rotating or pinching |
ongestureend | Fired when the user
lifts one or both fingers |
The same events are used for rotate and zoom gestures. All three
events receive a GestureEvent
parameter. This parameter has typical event properties, and the
additional properties scale and
rotation.
The scale property defines
the distance between the two fingers as a floating-point multiplier of
the initial distance when the gesture started. If this value is
greater than 1.0 it is a pinch open
(zoom in), and if it is lower than 1.0 it is a pinch close (zoom out).
The rotation value gives the
delta rotation from the initial point, in degrees. If the user is
rotating clockwise we will get a positive degree value, and we’ll get
a negative value for a counter-clockwise rotation.
I know what you’re thinking right now: “Great! Rotation and
zoom. But we’re working in HTML, so what we can do with that?” CSS
extensions for Safari on iOS (and other compatible browsers) come to
our help with one attribute, -webkit-transform, and two functions
available for manipulating its value: rotate and scale.
The rotate function receives
a parameter in degrees, and we need to define the deg unit after the number (e.g., rotate(90deg)). We can define it from a
script using element.style.webkitTransform.
Let’s look at a simple sample:
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Gesture Management</title>
<meta name="viewport" content="width=device-width; initial-scale=1.0;
maximum-scale=1.0; user-scalable=0;">
<script type="text/javascript">
function gesture(event) {
// We round values with two decimals
event.target.innerHTML = "Rotation: " + Math.round(event.rotation*100)/100
+ " Scale: " + Math.round(event.scale*100)/100;
// We apply the transform functions to the element
event.target.style.webkitTransform = "rotate(" + event.rotation%360 + "deg)" +
" scale(" + event.scale + ")";
}
</script>
</head>
<body>
<div ongesturechange="gesture(event)" style="background-color:silver; width: 300px;
height: 300px">
</div>
</body>
</html>
The sample works as shown in Figure 6. You can rotate
and scale the div (with all its
contents) using two fingers on compatible devices. What’s the only
problem? The transform style is always applied to the original
element. So, if we apply a scale of 2.0 to the element and later apply
a second scale of 0.5, the new scale value will be 0.5 and not 1.0, as
we might expect.
For typical zoom-rotate relative behavior, we should change our
function to the following:
<script type="text/javascript">
var rotation = 0;
var scale = 1;
function gesture(event) {
event.target.innerHTML = "Rotation: " +
Math.round((event.rotation+rotation)*100)/100
+ " Scale: " + Math.round((event.scale*scale)*100)/100;
event.target.style.webkitTransform = "rotate(" + (event.rotation+rotation)%360
+ "deg)" + " scale(" + event.scale*scale + ")";
}
function gestureend(event) {
rotation = event.rotation+rotation;
scale = event.scale*scale;
}
</script>
</head>
<body>
<div ongesturechange="gesture(event)" ongestureend="gestureend(event)"
style="background-color:silver; width: 100%; height: 300px">
</div>